package com.example.democonfigserver.config;

import lombok.Getter;
import lombok.ToString;
import org.springframework.context.MessageSource;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.ServletWebRequest;

import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.beans.Introspector;
import java.util.*;

/**
 * 全局异常处理类
 *
 * @author wangbin
 */
@ControllerAdvice
public class GlobalErrorHandler {

    @Resource
    private MessageSource messageSource;

    /**
     * 将controller中校验返回值转换成hibernate-validator相似的格式
     *
     * @param e       异常
     * @param request 请求
     * @return 影响体
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Map<String, Object> handleConstraintViolation(ConstraintViolationException e, ServletWebRequest request) {
        final Map<String, Object> result = getStringObjectMap(request, e.getMessage());
        result.put("errors", e.getConstraintViolations().stream().map(cv -> SimpleObjectError.from(cv, messageSource, request.getLocale())));
        return result;
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(DataIntegrityViolationException.class)
    @ResponseBody
    public Map<String, Object> handleDataIntegrityViolationException(DataIntegrityViolationException e, ServletWebRequest request) {
        final Map<String, Object> result = getStringObjectMap(request, e.getMessage());
        Map<String, Object> error = new HashMap<>();
        String constraintName = ((org.hibernate.exception.ConstraintViolationException) e.getCause()).getConstraintName();
        if (constraintName != null)
            switch (constraintName) {
                case "system_application.code":
                    error.put("defaultMessage", "\"应用编号\" 已经存在");
                    break;
                case "system_configuration.code":
                    error.put("defaultMessage", "\"配置编号\" 已经存在");
                    break;
                case "system_environment.code":
                    error.put("defaultMessage", "\"环境编号\" 已经存在");
                    break;
                case "system_project.code":
                    error.put("defaultMessage", "\"项目编号\" 已经存在");
                    break;
                default:
                    error.put("defaultMessage", "数据库唯一索引冲突，请检查参数");
            }
        result.put("errors", Collections.singletonList(error));
        return result;
    }

    private Map<String, Object> getStringObjectMap(ServletWebRequest request, String message) {
        final Map<String, Object> result = new LinkedHashMap<>();
        result.put("timestamp", new Date());
        result.put("path", request.getRequest().getRequestURI());
        result.put("status", HttpStatus.BAD_REQUEST.value());
        result.put("error", HttpStatus.BAD_REQUEST.getReasonPhrase());
        result.put("message", message);
        return result;
    }

    @Getter
    @ToString
    static class SimpleObjectError {
        String defaultMessage;
        String objectName;
        String field;
        Object rejectedValue;
        String code;

        public static SimpleObjectError from(ConstraintViolation<?> violation, MessageSource msgSrc, Locale locale) {
            SimpleObjectError result = new SimpleObjectError();
            result.defaultMessage = msgSrc.getMessage(violation.getMessageTemplate(),
                    new Object[]{violation.getLeafBean().getClass().getSimpleName(), violation.getPropertyPath().toString(),
                            violation.getInvalidValue()}, violation.getMessage(), locale);
            result.objectName = Introspector.decapitalize(violation.getRootBean().getClass().getSimpleName());
            result.field = String.valueOf(violation.getPropertyPath());
            result.rejectedValue = violation.getInvalidValue();
            result.code = violation.getMessageTemplate();
            return result;
        }
    }
}
